<script setup lang="ts">
import gettext from '@/gettext'
import StdDataEntry from '@/components/StdDataEntry'
import StdPagination from './StdPagination.vue'
import {computed, onMounted, reactive, ref, watch} from 'vue'
import {useRoute, useRouter} from 'vue-router'
import {message} from 'ant-design-vue'
import {downloadCsv} from '@/lib/helper'
import dayjs from 'dayjs'
import Sortable from 'sortablejs'
import {HolderOutlined} from '@ant-design/icons-vue'
import {toRaw} from '@vue/reactivity'

const {$gettext, interpolate} = gettext

const emit = defineEmits(['onSelected', 'onSelectedRecord', 'clickEdit', 'update:selectedRowKeys', 'clickBatchModify'])

const props = defineProps({
    api: Object,
    columns: Array,
    data_key: {
        type: String,
        default: 'data'
    },
    disable_search: {
        type: Boolean,
        default: false
    },
    disable_query_params: {
        type: Boolean,
        default: false
    },
    disable_add: {
        type: Boolean,
        default: false
    },
    edit_text: String,
    deletable: {
        type: Boolean,
        default: true
    },
    get_params: {
        type: Object,
        default() {
            return {}
        }
    },
    editable: {
        type: Boolean,
        default: true
    },
    selectionType: {
        type: String,
        validator: function (value: string) {
            return ['checkbox', 'radio'].indexOf(value) !== -1
        }
    },
    pithy: {
        type: Boolean,
        default: false
    },
    scrollX: {
        type: [Number, Boolean],
        default: true
    },
    rowKey: {
        type: String,
        default: 'id'
    },
    exportCsv: {
        type: Boolean,
        default: false
    },
    size: String,
    selectedRowKeys: {
        type: Array
    },
    useSortable: Boolean
})

const data_source: any = ref([])
const expand_keys_list: any = ref([])
const rows_key_index_map: any = ref({})

const loading = ref(true)
const pagination = reactive({
    total: 1,
    per_page: 10,
    current_page: 1,
    total_pages: 1
})

const route = useRoute()
const params = reactive({
    ...props.get_params
})

const selectedKeysLocalBuffer: any = ref([])

const selectedRowKeysBuffer = computed({
    get() {
        return props.selectedRowKeys || selectedKeysLocalBuffer.value
    },
    set(v) {
        selectedKeysLocalBuffer.value = v
        emit('update:selectedRowKeys', v)
    }
})

const searchColumns = getSearchColumns()
const pithyColumns = getPithyColumns()
const batchColumns = getBatchEditColumns()

onMounted(() => {
    if (!props.disable_query_params) {
        Object.assign(params, route.query)
    }
    get_list()

    if (props.useSortable) {
        initSortable()
    }
})

defineExpose({
    get_list
})

function destroy(id: any) {
    props.api!.destroy(id).then(() => {
        get_list()
        message.success(interpolate($gettext('Delete ID: %{id}'), {id: id}))
    }).catch((e: any) => {
        message.error($gettext(e?.message ?? 'Server error'))
    })
}

function get_list(page_num = null, page_size = 20) {
    loading.value = true
    if (page_num) {
        params['page'] = page_num
        params['page_size'] = page_size
    }
    props.api!.get_list(params).then(async (r: any) => {
        data_source.value = r.data
        rows_key_index_map.value = {}
        if (props.useSortable) {
            function buildIndexMap(data: any, level: number = 0, index: number = 0, total: number[] = []) {
                if (data && data.length > 0) {
                    data.forEach((v: any) => {
                        v.level = level
                        let current_index = [...total, index++]
                        rows_key_index_map.value[v.id] = current_index
                        if (v.children) buildIndexMap(v.children, level + 1, 0, current_index)
                    })
                }
            }

            buildIndexMap(r.data)
        }

        if (r.pagination !== undefined) {
            Object.assign(pagination, r.pagination)
        }

        loading.value = false
    }).catch((e: any) => {
        message.error(e?.message ?? $gettext('Server error'))
    })
}

function stdChange(pagination: any, filters: any, sorter: any) {
    if (sorter) {
        selectedRowKeysBuffer.value = []
        params['order_by'] = sorter.field
        params['sort'] = sorter.order === 'ascend' ? 'asc' : 'desc'
        switch (sorter.order) {
            case 'ascend':
                params['sort'] = 'asc'
                break
            case 'descend':
                params['sort'] = 'desc'
                break
            default:
                params['sort'] = null
                break
        }
    }
    if (pagination) {
        selectedRowKeysBuffer.value = []
    }
}

function expandedTable(keys: any) {
    expand_keys_list.value = keys
}

function getSearchColumns() {
    let searchColumns: any = []
    props.columns!.forEach((column: any) => {
        if (column.search) {
            searchColumns.push(column)
        }
    })
    return searchColumns
}

function getBatchEditColumns() {
    let batch: any = []
    props.columns!.forEach((column: any) => {
        if (column.batch) {
            batch.push(column)
        }
    })
    return batch
}

function getPithyColumns() {
    if (props.pithy) {
        return props.columns!.filter((c: any, index: any, columns: any) => {
            return c.pithy === true && c.display !== false
        })
    }
    return props.columns!.filter((c: any, index: any, columns: any) => {
        return c.display !== false
    })
}

function checked(c: any) {
    params[c.target.value] = c.target.checked
}

const crossPageSelect: any = {}

async function onSelectChange(_selectedRowKeys: any) {
    const page = params.page || 1

    crossPageSelect[page] = await _selectedRowKeys

    let t: any = []
    Object.keys(crossPageSelect).forEach(v => {
        t.push(...crossPageSelect[v])
    })
    const n: any = [..._selectedRowKeys]
    t = await t.concat(n)
    // console.log(crossPageSelect)
    const set = new Set(t)
    selectedRowKeysBuffer.value = Array.from(set)
    emit('onSelected', selectedRowKeysBuffer.value)
}

function onSelect(record: any) {
    emit('onSelectedRecord', record)
}

const router = useRouter()

const reset_search = async () => {
    Object.keys(params).forEach(v => {
        delete params[v]
    })

    Object.assign(params, {
        ...props.get_params
    })

    router.push({query: {}}).catch(() => {
    })
}

watch(params, () => {
    if (!props.disable_query_params) {
        router.push({query: params})
    }
    get_list()
})

const rowSelection = computed(() => {
    if (batchColumns.length > 0 || props.selectionType) {
        return {
            selectedRowKeys: selectedRowKeysBuffer.value, onChange: onSelectChange,
            onSelect: onSelect, type: batchColumns.length > 0 ? 'checkbox' : props.selectionType
        }
    } else {
        return null
    }
})

function fn(obj: Object, desc: string) {
    const arr: string[] = desc.split('.')
    while (arr.length) {
        // @ts-ignore
        const top = obj[arr.shift()]
        if (top === undefined) {
            return null
        }
        obj = top
    }
    return obj
}

async function export_csv() {
    let header = []
    let headerKeys: any[] = []
    const showColumnsMap: any = {}
    // @ts-ignore
    for (let showColumnsKey in pithyColumns) {
        // @ts-ignore
        if (pithyColumns[showColumnsKey].dataIndex === 'action') continue
        // @ts-ignore
        let t = pithyColumns[showColumnsKey].title

        if (typeof t === 'function') {
            t = t()
        }
        header.push({
            title: t,
            // @ts-ignore
            key: pithyColumns[showColumnsKey].dataIndex
        })
        // @ts-ignore
        headerKeys.push(pithyColumns[showColumnsKey].dataIndex)
        // @ts-ignore
        showColumnsMap[pithyColumns[showColumnsKey].dataIndex] = pithyColumns[showColumnsKey]
    }

    let dataSource: any = []
    let hasMore = true
    let page = 1
    while (hasMore) {
        // 准备 DataSource
        await props.api!.get_list({page}).then((response: any) => {
            if (response.data.length === 0) {
                hasMore = false
                return
            }
            if (response[props.data_key] === undefined) {
                dataSource = dataSource.concat(...response.data)
            } else {
                dataSource = dataSource.concat(...response[props.data_key])
            }
        }).catch((e: any) => {
            message.error(e.message ?? $gettext('Server error'))
            hasMore = false
            return
        })
        page += 1
    }
    const data: any[] = []
    dataSource.forEach((row: Object) => {
        let obj: any = {}
        headerKeys.forEach(key => {
            let data = fn(row, key)
            const c = showColumnsMap[key]
            data = c?.customRender?.({text: data}) ?? data
            obj[c.dataIndex] = data
        })
        data.push(obj)
    })

    downloadCsv(header, data,
        `${$gettext('Export')}-${dayjs().format('YYYYMMDDHHmmss')}.csv`)
}

const hasSelectedRow = computed(() => {
    return batchColumns.length > 0 && selectedRowKeysBuffer.value.length > 0
})

function click_batch_edit() {
    emit('clickBatchModify', batchColumns, selectedRowKeysBuffer.value)
}

function getLeastIndex(index: number) {
    return index >= 1 ? index : 1
}

function getTargetData(data: any, indexList: number[]): any {
    let target: any = {children: data}
    indexList.forEach((index: number) => {
        target.children[index].parent = target
        target = target.children[index]
    })
    return target
}

function initSortable() {
    const table: any = document.querySelector('#std-table tbody')
    new Sortable(table, {
        handle: '.ant-table-drag-icon',
        animation: 150,
        sort: true,
        forceFallback: true,
        setData: function (dataTransfer) {
            dataTransfer.setData('Text', '')
        },
        onStart({item}) {
            let targetRowKey = Number(item.dataset.rowKey)
            if (targetRowKey) {
                expand_keys_list.value = expand_keys_list.value.filter((item: number) => item !== targetRowKey)
            }
        },
        onMove({dragged, related}) {
            const oldRow: number[] = rows_key_index_map.value?.[Number(dragged.dataset.rowKey)]
            const newRow: number[] = rows_key_index_map.value?.[Number(related.dataset.rowKey)]
            if (oldRow.length !== newRow.length || oldRow[oldRow.length - 2] != newRow[newRow.length - 2]) {
                return false
            }
        },
        async onEnd({item, newIndex, oldIndex}) {
            if (newIndex === oldIndex) return

            const indexDelta: number = Number(oldIndex) - Number(newIndex)
            const direction: number = indexDelta > 0 ? +1 : -1

            let rowIndex: number[] = rows_key_index_map.value?.[Number(item.dataset.rowKey)]
            const newRow = getTargetData(data_source.value, rowIndex)
            const newRowParent = newRow.parent
            const level: number = newRow.level

            let currentRowIndex: number[] = [...rows_key_index_map.value?.
                [Number(table.children[Number(newIndex) + direction].dataset.rowKey)]]
            let currentRow: any = getTargetData(data_source.value, currentRowIndex)
            // Reset parent
            currentRow.parent = newRow.parent = null
            newRowParent.children.splice(rowIndex[level], 1)
            newRowParent.children.splice(currentRowIndex[level], 0, toRaw(newRow))

            let changeIds: number[] = []

            function processChanges(row: any, children: boolean = false, newIndex: number | undefined = undefined) {
                // Build changes ID list expect new row
                if (children || newIndex === undefined) changeIds.push(row.id)

                if (newIndex !== undefined)
                    rows_key_index_map.value[row.id][level] = newIndex
                else if (children)
                    rows_key_index_map.value[row.id][level] += direction

                row.parent = null
                if (row.children) {
                    row.children.forEach((v: any) => processChanges(v, true, newIndex))
                }
            }

            // Replace row index for new row
            processChanges(newRow, false, currentRowIndex[level])
            // Rebuild row index maps for changes row
            for (let i = Number(oldIndex); i != newIndex; i -= direction) {
                let rowIndex: number[] = rows_key_index_map.value?.[table.children[i].dataset.rowKey]
                rowIndex[level] += direction
                processChanges(getTargetData(data_source.value, rowIndex))
            }
            console.log('Change row id', newRow.id, 'order', newRow.id, '=>', currentRow.id, ', direction: ', direction,
                ', changes IDs:', changeIds)

            props.api!.update_order({
                target_id: newRow.id,
                direction: direction,
                affected_ids: changeIds
            }).then(() => {
                message.success($gettext('Updated successfully'))
            }).catch((e: any) => {
                message.error(e?.message ?? $gettext('Server error'))
            })
        }
    })
}


</script>

<template>
    <div class="std-table">
        <std-data-entry
            v-if="!disable_search && searchColumns.length"
            :data-list="searchColumns"
            :data-source="params"
            layout="inline"
        >
            <template #action>
                <a-space class="action-btn">
                    <a-button v-if="exportCsv" @click="export_csv" type="primary" ghost>
                        {{ $gettext('Export') }}
                    </a-button>
                    <a-button @click="reset_search">
                        {{ $gettext('Reset') }}
                    </a-button>
                    <a-button v-if="hasSelectedRow" @click="click_batch_edit">
                        {{ $gettext('Batch Modify') }}
                    </a-button>
                </a-space>
            </template>
        </std-data-entry>
        <a-table
            :columns="pithyColumns"
            :data-source="data_source"
            :loading="loading"
            :pagination="false"
            :row-key="rowKey"
            :rowSelection="rowSelection"
            @change="stdChange"
            :scroll="{ x: scrollX }"
            :size="size"
            id="std-table"
            @expandedRowsChange="expandedTable"
            :expandedRowKeys="expand_keys_list"
        >
            <template
                v-slot:bodyCell="{text, record, index, column}"
            >
                <template v-if="column.handle === true">
                    <span class="ant-table-drag-icon"><HolderOutlined/></span>
                    {{ text }}
                </template>
                <template v-if="column.dataIndex === 'action'">
                    <a-button type="link" size="small" v-if="props.editable"
                              @click="$emit('clickEdit', record[props.rowKey], record)">
                        {{ props.edit_text || $gettext('Modify') }}
                    </a-button>
                    <slot name="actions" :record="record"/>
                    <template v-if="props.deletable">
                        <a-divider type="vertical"/>
                        <a-popconfirm
                            :cancelText="$gettext('No')"
                            :okText="$gettext('OK')"
                            :title="$gettext('Are you sure you want to delete?')"
                            @confirm="destroy(record[rowKey])">
                            <a-button type="link" size="small">{{ $gettext('Delete') }}</a-button>
                        </a-popconfirm>
                    </template>
                </template>
            </template>
        </a-table>
        <std-pagination :size="size" :pagination="pagination" @change="get_list" @changePageSize="stdChange"/>
    </div>
</template>

<style lang="less">
.ant-table-scroll {
    .ant-table-body {
        overflow-x: auto !important;
    }
}
</style>

<style lang="less" scoped>
.ant-form {
    margin: 10px 0 20px 0;
}

.ant-slider {
    min-width: 90px;
}

.std-table {
    .ant-table-wrapper {
        // overflow-x: scroll;
    }
}

.action-btn {
    // min-height: 50px;
    height: 100%;
    display: flex;
    align-items: flex-start;
}

:deep(.ant-form-inline .ant-form-item) {
    margin-bottom: 10px;
}
</style>

<style lang="less">
.ant-table-drag-icon {
    float: left;
    margin-right: 16px;
    cursor: grab;
}

.sortable-ghost *, .sortable-chosen * {
    cursor: grabbing !important;
}
</style>
